package settings

import (
	"context"

	"connectrpc.com/connect"
	"google.golang.org/protobuf/types/known/timestamppb"

	"github.com/zitadel/zitadel/internal/api/authz"
	"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
	"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
	"github.com/zitadel/zitadel/internal/domain"
	"github.com/zitadel/zitadel/internal/i18n"
	"github.com/zitadel/zitadel/internal/query"
	"github.com/zitadel/zitadel/internal/zerrors"
	filter_pb "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
	object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2"
	"github.com/zitadel/zitadel/pkg/grpc/settings/v2"
)

func (s *Server) GetLoginSettings(ctx context.Context, req *connect.Request[settings.GetLoginSettingsRequest]) (*connect.Response[settings.GetLoginSettingsResponse], error) {
	current, err := s.query.LoginPolicyByID(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
	if err != nil {
		return nil, err
	}
	return connect.NewResponse(&settings.GetLoginSettingsResponse{
		Settings: loginSettingsToPb(current),
		Details: &object_pb.Details{
			Sequence:      current.Sequence,
			CreationDate:  timestamppb.New(current.CreationDate),
			ChangeDate:    timestamppb.New(current.ChangeDate),
			ResourceOwner: current.OrgID,
		},
	}), nil
}

func (s *Server) GetPasswordComplexitySettings(ctx context.Context, req *connect.Request[settings.GetPasswordComplexitySettingsRequest]) (*connect.Response[settings.GetPasswordComplexitySettingsResponse], error) {
	current, err := s.query.PasswordComplexityPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
	if err != nil {
		return nil, err
	}
	return connect.NewResponse(&settings.GetPasswordComplexitySettingsResponse{
		Settings: passwordComplexitySettingsToPb(current),
		Details: &object_pb.Details{
			Sequence:      current.Sequence,
			CreationDate:  timestamppb.New(current.CreationDate),
			ChangeDate:    timestamppb.New(current.ChangeDate),
			ResourceOwner: current.ResourceOwner,
		},
	}), nil
}

func (s *Server) GetPasswordExpirySettings(ctx context.Context, req *connect.Request[settings.GetPasswordExpirySettingsRequest]) (*connect.Response[settings.GetPasswordExpirySettingsResponse], error) {
	current, err := s.query.PasswordAgePolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
	if err != nil {
		return nil, err
	}
	return connect.NewResponse(&settings.GetPasswordExpirySettingsResponse{
		Settings: passwordExpirySettingsToPb(current),
		Details: &object_pb.Details{
			Sequence:      current.Sequence,
			CreationDate:  timestamppb.New(current.CreationDate),
			ChangeDate:    timestamppb.New(current.ChangeDate),
			ResourceOwner: current.ResourceOwner,
		},
	}), nil
}

func (s *Server) GetBrandingSettings(ctx context.Context, req *connect.Request[settings.GetBrandingSettingsRequest]) (*connect.Response[settings.GetBrandingSettingsResponse], error) {
	current, err := s.query.ActiveLabelPolicyByOrg(ctx, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
	if err != nil {
		return nil, err
	}
	return connect.NewResponse(&settings.GetBrandingSettingsResponse{
		Settings: brandingSettingsToPb(current, s.assetsAPIDomain(ctx)),
		Details: &object_pb.Details{
			Sequence:      current.Sequence,
			CreationDate:  timestamppb.New(current.CreationDate),
			ChangeDate:    timestamppb.New(current.ChangeDate),
			ResourceOwner: current.ResourceOwner,
		},
	}), nil
}

func (s *Server) GetDomainSettings(ctx context.Context, req *connect.Request[settings.GetDomainSettingsRequest]) (*connect.Response[settings.GetDomainSettingsResponse], error) {
	current, err := s.query.DomainPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
	if err != nil {
		return nil, err
	}
	return connect.NewResponse(&settings.GetDomainSettingsResponse{
		Settings: domainSettingsToPb(current),
		Details: &object_pb.Details{
			Sequence:      current.Sequence,
			CreationDate:  timestamppb.New(current.CreationDate),
			ChangeDate:    timestamppb.New(current.ChangeDate),
			ResourceOwner: current.ResourceOwner,
		},
	}), nil
}

func (s *Server) GetLegalAndSupportSettings(ctx context.Context, req *connect.Request[settings.GetLegalAndSupportSettingsRequest]) (*connect.Response[settings.GetLegalAndSupportSettingsResponse], error) {
	current, err := s.query.PrivacyPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), false)
	if err != nil {
		return nil, err
	}
	return connect.NewResponse(&settings.GetLegalAndSupportSettingsResponse{
		Settings: legalAndSupportSettingsToPb(current),
		Details: &object_pb.Details{
			Sequence:      current.Sequence,
			CreationDate:  timestamppb.New(current.CreationDate),
			ChangeDate:    timestamppb.New(current.ChangeDate),
			ResourceOwner: current.ResourceOwner,
		},
	}), nil
}

func (s *Server) GetLockoutSettings(ctx context.Context, req *connect.Request[settings.GetLockoutSettingsRequest]) (*connect.Response[settings.GetLockoutSettingsResponse], error) {
	current, err := s.query.LockoutPolicyByOrg(ctx, true, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()))
	if err != nil {
		return nil, err
	}
	return connect.NewResponse(&settings.GetLockoutSettingsResponse{
		Settings: lockoutSettingsToPb(current),
		Details: &object_pb.Details{
			Sequence:      current.Sequence,
			CreationDate:  timestamppb.New(current.CreationDate),
			ChangeDate:    timestamppb.New(current.ChangeDate),
			ResourceOwner: current.ResourceOwner,
		},
	}), nil
}

func (s *Server) GetActiveIdentityProviders(ctx context.Context, req *connect.Request[settings.GetActiveIdentityProvidersRequest]) (*connect.Response[settings.GetActiveIdentityProvidersResponse], error) {
	queries, err := activeIdentityProvidersToQuery(req.Msg)
	if err != nil {
		return nil, err
	}

	links, err := s.query.IDPLoginPolicyLinks(ctx, object.ResourceOwnerFromReq(ctx, req.Msg.GetCtx()), &query.IDPLoginPolicyLinksSearchQuery{Queries: queries}, false)
	if err != nil {
		return nil, err
	}

	return connect.NewResponse(&settings.GetActiveIdentityProvidersResponse{
		Details:           object.ToListDetails(links.SearchResponse),
		IdentityProviders: identityProvidersToPb(links.Links),
	}), nil
}

func activeIdentityProvidersToQuery(req *settings.GetActiveIdentityProvidersRequest) (_ []query.SearchQuery, err error) {
	q := make([]query.SearchQuery, 0, 4)
	if req.CreationAllowed != nil {
		creationQuery, err := query.NewIDPTemplateIsCreationAllowedSearchQuery(*req.CreationAllowed)
		if err != nil {
			return nil, err
		}
		q = append(q, creationQuery)
	}
	if req.LinkingAllowed != nil {
		creationQuery, err := query.NewIDPTemplateIsLinkingAllowedSearchQuery(*req.LinkingAllowed)
		if err != nil {
			return nil, err
		}
		q = append(q, creationQuery)
	}
	if req.AutoCreation != nil {
		creationQuery, err := query.NewIDPTemplateIsAutoCreationSearchQuery(*req.AutoCreation)
		if err != nil {
			return nil, err
		}
		q = append(q, creationQuery)
	}
	if req.AutoLinking != nil {
		compare := query.NumberEquals
		if *req.AutoLinking {
			compare = query.NumberNotEquals
		}
		creationQuery, err := query.NewIDPTemplateAutoLinkingSearchQuery(0, compare)
		if err != nil {
			return nil, err
		}
		q = append(q, creationQuery)
	}
	return q, nil
}

func (s *Server) GetGeneralSettings(ctx context.Context, _ *connect.Request[settings.GetGeneralSettingsRequest]) (*connect.Response[settings.GetGeneralSettingsResponse], error) {
	instance := authz.GetInstance(ctx)
	return connect.NewResponse(&settings.GetGeneralSettingsResponse{
		SupportedLanguages: domain.LanguagesToStrings(i18n.SupportedLanguages()),
		DefaultOrgId:       instance.DefaultOrganisationID(),
		DefaultLanguage:    instance.DefaultLanguage().String(),
	}), nil
}

func (s *Server) GetSecuritySettings(ctx context.Context, req *connect.Request[settings.GetSecuritySettingsRequest]) (*connect.Response[settings.GetSecuritySettingsResponse], error) {
	policy, err := s.query.SecurityPolicy(ctx)
	if err != nil {
		return nil, err
	}
	return connect.NewResponse(&settings.GetSecuritySettingsResponse{
		Settings: securityPolicyToSettingsPb(policy),
	}), nil
}

func (s *Server) GetHostedLoginTranslation(ctx context.Context, req *connect.Request[settings.GetHostedLoginTranslationRequest]) (*connect.Response[settings.GetHostedLoginTranslationResponse], error) {
	translation, err := s.query.GetHostedLoginTranslation(ctx, req.Msg)
	if err != nil {
		return nil, err
	}

	return connect.NewResponse(translation), nil
}

func (s *Server) ListOrganizationSettings(ctx context.Context, req *connect.Request[settings.ListOrganizationSettingsRequest]) (*connect.Response[settings.ListOrganizationSettingsResponse], error) {
	queries, err := s.listOrganizationSettingsRequestToModel(req.Msg)
	if err != nil {
		return nil, err
	}
	resp, err := s.query.SearchOrganizationSettings(ctx, queries, s.checkPermission)
	if err != nil {
		return nil, err
	}
	return connect.NewResponse(&settings.ListOrganizationSettingsResponse{
		OrganizationSettings: organizationSettingsListToPb(resp.OrganizationSettingsList),
		Pagination:           filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
	}), nil
}

func (s *Server) listOrganizationSettingsRequestToModel(req *settings.ListOrganizationSettingsRequest) (*query.OrganizationSettingsSearchQueries, error) {
	offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
	if err != nil {
		return nil, err
	}
	queries, err := organizationSettingsFiltersToQuery(req.Filters)
	if err != nil {
		return nil, err
	}
	return &query.OrganizationSettingsSearchQueries{
		SearchRequest: query.SearchRequest{
			Offset:        offset,
			Limit:         limit,
			Asc:           asc,
			SortingColumn: organizationSettingsFieldNameToSortingColumn(req.SortingColumn),
		},
		Queries: queries,
	}, nil
}

func organizationSettingsFieldNameToSortingColumn(field *settings.OrganizationSettingsFieldName) query.Column {
	if field == nil {
		return query.OrganizationSettingsColumnCreationDate
	}
	switch *field {
	case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_CREATION_DATE:
		return query.OrganizationSettingsColumnCreationDate
	case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_ORGANIZATION_ID:
		return query.OrganizationSettingsColumnID
	case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_CHANGE_DATE:
		return query.OrganizationSettingsColumnChangeDate
	case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_UNSPECIFIED:
		return query.OrganizationSettingsColumnCreationDate
	default:
		return query.OrganizationSettingsColumnCreationDate
	}
}

func organizationSettingsFiltersToQuery(queries []*settings.OrganizationSettingsSearchFilter) (_ []query.SearchQuery, err error) {
	q := make([]query.SearchQuery, len(queries))
	for i, qry := range queries {
		q[i], err = organizationSettingsToModel(qry)
		if err != nil {
			return nil, err
		}
	}
	return q, nil
}

func organizationSettingsToModel(filter *settings.OrganizationSettingsSearchFilter) (query.SearchQuery, error) {
	switch q := filter.Filter.(type) {
	case *settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter:
		return organizationInIDsFilterToQuery(q.InOrganizationIdsFilter)
	case *settings.OrganizationSettingsSearchFilter_OrganizationScopedUsernamesFilter:
		return organizationScopedUsernamesFilterToQuery(q.OrganizationScopedUsernamesFilter)
	default:
		return nil, zerrors.ThrowInvalidArgument(nil, "SETTINGS-uvTDqZHlvS", "List.Query.Invalid")
	}
}

func organizationInIDsFilterToQuery(q *filter_pb.InIDsFilter) (query.SearchQuery, error) {
	return query.NewOrganizationSettingsOrganizationIDSearchQuery(q.Ids)
}

func organizationScopedUsernamesFilterToQuery(q *settings.OrganizationScopedUsernamesFilter) (query.SearchQuery, error) {
	return query.NewOrganizationSettingsOrganizationScopedUsernamesSearchQuery(q.OrganizationScopedUsernames)
}

func organizationSettingsListToPb(settingsList []*query.OrganizationSettings) []*settings.OrganizationSettings {
	o := make([]*settings.OrganizationSettings, len(settingsList))
	for i, organizationSettings := range settingsList {
		o[i] = organizationSettingsToPb(organizationSettings)
	}
	return o
}

func organizationSettingsToPb(organizationSettings *query.OrganizationSettings) *settings.OrganizationSettings {
	return &settings.OrganizationSettings{
		OrganizationId:              organizationSettings.ID,
		CreationDate:                timestamppb.New(organizationSettings.CreationDate),
		ChangeDate:                  timestamppb.New(organizationSettings.ChangeDate),
		OrganizationScopedUsernames: organizationSettings.OrganizationScopedUsernames,
	}
}
